#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <ctype.h>
#define MAXSIZE 2000

#include "page.h"
#include "http.h"

const char* get_file_type(const char *name)
{
    const char* dot;
    // 自右向左查找‘.’字符, 如不存在返回NULL
    dot = strrchr(name, '.');   
    if (dot == NULL)
        return "text/plain;charset=utf-8";
    else if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html;charset=utf-8";
    else if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    else if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    else if (strcmp(dot, ".png") == 0)
        return "image/png";
    else if (strcmp(dot, ".css") == 0)
        return "text/css";
    else if (strcmp(dot, ".js") == 0)
        return "application/x-javascript";

    return "text/plain;charset=utf-8";
}

// 断开连接的函数
void disconnect(int cfd, int epfd)
{
    int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    if(ret == -1)
    {
        perror("epoll_ctl del cfd error");
        exit(1);
    }
    close(cfd);
}

// 发送响应头
void send_respond_head(int cfd, int no, const char* desp, const char* type, long len)
{
    printf("\033[32m============= RESPONSE HEADER ============\n\033[0m");
    char buf[1024] = {0};
    // 状态行
    sprintf(buf, "HTTP/1.1 %d %s\r\n", no, desp);
    printf("%s", buf);
    send(cfd, buf, strlen(buf), 0);
    // 消息报头
    sprintf(buf, "Content-Type:%s\r\n", type);
    sprintf(buf+strlen(buf), "Content-Length:%ld\r\n", len);
    printf("%s", buf);
    send(cfd, buf, strlen(buf), 0);
    // 空行
    printf("\r\n");
    send(cfd, "\r\n", 2, 0);
}

// 发送文件
void send_file(int cfd, const char* filename)
{
    // 打开文件
    int fd = open(filename, O_RDONLY);
    if(fd == -1)
    {
        // show 404
        return;
    }
    // 循环读文件
    char buf[4096] = {0};
    int len = 0;
    while( (len = read(fd, buf, sizeof(buf))) > 0 )
    {
        // 发送读出的数据
        send(cfd, buf, len, 0);
    }
    if(len == -1)
    {
        perror("read file error");
        exit(1);
    }
    close(fd);
}

// http请求处理
void http_get_request(const char* request, int cfd, const char* root)
{
    printf("\033[32m============= REQUEST PARSING ============\n\033[0m");
    char method[12], path[1024], protocol[12];
    sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);//匹配非空
    printf("method = %s, path = %s, protocol = %s\n", method, path, protocol);
    char* file = path;
    if(strcmp(path, "/") == 0) {
        send_respond_head(cfd, 200, "欢迎", "text/html;charset=utf-8", -1);
        to_index_path(cfd);
    }
    else {
        // 获取文件属性
        printf("\033[42m%s %s\033[0m\n", path, root);
        if(strncmp(path, root, strlen(root)) == 0) {
            printf("\033[42m%s %s\033[0m\n", path, root);
            memmove(path, path+strlen(root), strlen(path)-strlen(root)+1);
            printf("\033[42m%s %s\033[0m\n", path, root);
        }
        struct stat st;
        int ret = stat(file, &st);
        if(ret == -1)
        {
            send_respond_head(cfd, 404, "File Not Found", "text/html;charset=utf-8", -1);
            to_notfound_page(cfd);
        }
        else if(S_ISREG(st.st_mode))
        {
            send_respond_head(cfd, 200, "OK", get_file_type(file), st.st_size);
            send_file(cfd, file);
        }
    }
}

void http_post_request(const char* request, int cfd)
{
    // Read the post line.
    char buf[1024];
    int len = get_line(cfd, buf, 1024);
    printf("\033[32m============= POST LINE ============\n\033[0m");
    printf("%s\n", buf);
    
    send_respond_head(cfd, 200, "OK", "text/html;charset=utf-8", -1);

    char *asterisk = strchr(buf, '&');
    char username[128];
    char passwd[128];
    char *equal1 = strchr(buf, '=');
    char *equal2 = strchr(asterisk, '=');
    int unamelen = asterisk-equal1-1;
    int pswdlen = strlen(equal2+1);
    
    strncpy(username, equal1+1, unamelen);
    // +1 to skip '=', notice the |strlen()| include the '\n'
    strncpy(passwd, equal2+1, pswdlen);
    username[unamelen] = '\0';
    passwd[pswdlen] = '\0';
    printf("%s\n", username);
    printf("%s\n", passwd);

    to_post_page(cfd, username, passwd);
}

// 解析http请求消息的每一行内容
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;
    while ((i < size - 1) && (c != '\n'))
    {
        n = recv(sock, &c, 1, 0);
        if (n > 0)
        {
            buf[i] = c;
            i++;
        }
        else
        {
            c = '\n';
        }
    }
    buf[i] = '\0';
    return i;
}

void do_read(int cfd, int epfd, const char *root)
{
    // 将浏览器发过来的数据, 读到buf中 
    char line[1024] = {0};
    // 读请求行
    int len = get_line(cfd, line, sizeof(line));
    if(len == 0)
    {
        printf("客户端断开了连接...\n");
        // 关闭套接字, cfd从epoll上del
        disconnect(cfd, epfd);         
    }
    else
    {
        printf("\033[32m============= REQUEST LINE ============\n\033[0m");
        printf("%s", line);
        printf("\033[32m============= REQUEST HEADER ============\n\033[0m");
        while(len)
        {
            char buf[1024] = {0};
            len = get_line(cfd, buf, sizeof(buf));
            if(strcmp("\r\n", buf) == 0)
            {
                // Response header ends here
                printf("\033[32mnewline (end of request header)\033[0m\n");
                break;
            }
            printf("%s", buf);
        }
        printf("\033[32m============= REQUEST HEADER END ============\n\033[0m");
    }

    if(strncasecmp("get", line, 3) == 0)
    {
        http_get_request(line, cfd, root);
        disconnect(cfd, epfd);         
    }
    else if(strncasecmp("post", line, 4) == 0)
    {
        http_post_request(line, cfd);
        disconnect(cfd, epfd);
    }

}


// 接受新连接处理
void do_accept(int lfd, int epfd)
{
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int cfd = accept(lfd, (struct sockaddr*)&client, &len);
    if(cfd == -1)
    {
        perror("accept error");
        exit(1);
    }

    // 打印客户端信息
    char ip[64] = {0};
    // 设置cfd为非阻塞
    int flag = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(cfd, F_SETFL, flag);
    // 得到的新节点挂到epoll树上
    struct epoll_event ev;
    ev.data.fd = cfd;
    // 边沿非阻塞模式
    ev.events = EPOLLIN | EPOLLET;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    if(ret == -1)
    {
        perror("epoll_ctl add cfd error");
        exit(1);
    }
}

int init_listen_fd(int port, int epfd)
{
    //　套接
    int lfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   // 绑定
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(port);
    serv.sin_addr.s_addr = INADDR_ANY;
    // 端口复用
    int flag = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    int ret = bind(lfd, (struct sockaddr *)&serv, sizeof(serv));
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }
    // 设置监听
    ret = listen(lfd, 64);
    if(ret == -1)
    {
        perror("listen error");
        exit(1);
    }
    // lfd添加到epoll树上
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    if(ret == -1)
    {
        perror("epoll_ctl add lfd error");
        exit(1);
    }
    return lfd;

}

void run_epoll(int port, char* path, const char *root)
{
    int epfd = epoll_create(MAXSIZE);
    // 添加要监听的节点
    // 先添加监听lfd
    int lfd = init_listen_fd(port, epfd);

    // 委托内核检测添加到树上的节点
    struct epoll_event all[MAXSIZE];
    while(1)
    {
       int ret = epoll_wait(epfd, all, MAXSIZE, -1);
        if(ret == -1)
        {
            perror("epoll_wait error");
            exit(1);
        }

        // 遍历发生变化的节点
        for(int i=0; i<ret; ++i)
        {
            // 只处理读事件, 其他事件默认不处理
            struct epoll_event *pev = &all[i];
            if(!(pev->events & EPOLLIN))
            {
                // 不是读事件
                continue;
            }
            if(pev->data.fd == lfd)
            {
               // 接受连接请求
                do_accept(lfd, epfd);
            }
            else
            {
               // 读数据
                do_read(pev->data.fd, epfd, root);
            }
        }
    }
}